package org.dromara.web.service.impl

import cn.dev33.satoken.secure.BCrypt
import cn.dev33.satoken.stp.StpUtil
import io.github.oshai.kotlinlogging.KotlinLogging
import org.dromara.common.core.constant.Constants
import org.dromara.common.core.constant.GlobalConstants
import org.dromara.common.core.domain.model.LoginUser
import org.dromara.common.core.domain.model.PasswordLoginBody
import org.dromara.common.core.enums.LoginType
import org.dromara.common.core.exception.user.CaptchaException
import org.dromara.common.core.exception.user.CaptchaExpireException
import org.dromara.common.core.utils.MessageUtils
import org.dromara.common.core.utils.StringUtils
import org.dromara.common.core.utils.ValidatorUtils.validate
import org.dromara.common.json.utils.JsonUtils
import org.dromara.common.redis.utils.RedisUtils
import org.dromara.common.satoken.utils.LoginHelper
import org.dromara.common.tenant.helper.TenantHelper
import org.dromara.common.web.config.properties.CaptchaProperties
import org.dromara.system.domain.SysUser
import org.dromara.system.domain.vo.SysClientVo
import org.dromara.system.mapper.SysUserMapper
import org.dromara.web.domain.vo.LoginVo
import org.dromara.web.service.IAuthStrategy
import org.dromara.web.service.SysLoginService
import org.springframework.stereotype.Service

/**
 *@author LikeYouDo
 *@date 2025/1/3 10:47
 */
@Service("password" + IAuthStrategy.BASE_NAME)
class PasswordAuthStrategy(
    private val captchaProperties: CaptchaProperties,
    private val loginService: SysLoginService,
    private val userMapper: SysUserMapper,
) : IAuthStrategy {

    companion object {
        private val log = KotlinLogging.logger {}
    }

    override fun login(body: String, client: SysClientVo): LoginVo {
        val loginBody = JsonUtils.parseObject(body, PasswordLoginBody::class.java)
        validate(loginBody)
        val tenantId = loginBody.tenantId
        val username = loginBody.username
        val password = loginBody.password
        val code = loginBody.code
        val uuid = loginBody.uuid

        val captchaEnabled: Boolean = captchaProperties.enable
        // 验证码开关
        if (captchaEnabled) {
            validateCaptcha(tenantId, username, code, uuid)
        }
        val loginUser = TenantHelper.dynamic<LoginUser>(tenantId) {
            val user = loadUserByCondition(userMapper, SysUser::getUserName, username, log)
            loginService.checkLogin(LoginType.PASSWORD, tenantId, username) {
                !BCrypt.checkpw(password, user.password)
            }
            loginService.buildLoginUser(user)
        }.apply {
            clientKey = client.clientKey
            deviceType = client.deviceType
        }
        val model = createSaLoginModel(client)
        // 生成token
        LoginHelper.login(loginUser, model)

        val loginVo = LoginVo().apply {
            accessToken = StpUtil.getTokenValue()
            expireIn = StpUtil.getTokenTimeout()
            clientId = client.clientId
        }
        return loginVo
    }

    /**
     * 校验验证码
     *
     * @param username 用户名
     * @param code     验证码
     * @param uuid     唯一标识
     */
    private fun validateCaptcha(tenantId: String?, username: String?, code: String?, uuid: String?) {
        val verifyKey = GlobalConstants.CAPTCHA_CODE_KEY + StringUtils.blankToDefault(uuid, "")
        val captcha = RedisUtils.getCacheObject<String?>(verifyKey)
        RedisUtils.deleteObject(verifyKey)
        if (captcha == null) {
            loginService.recordLogininfor(
                tenantId,
                username,
                Constants.LOGIN_FAIL,
                MessageUtils.message("user.jcaptcha.expire")
            )
            throw CaptchaExpireException()
        }
        if (!StringUtils.equalsIgnoreCase(code, captcha)) {
            loginService.recordLogininfor(
                tenantId,
                username,
                Constants.LOGIN_FAIL,
                MessageUtils.message("user.jcaptcha.error")
            )
            throw CaptchaException()
        }
    }

}
